-- | The full generic Style file for the linear algebra domain
-- | Author: Dor Ma'ayan
--   August, 2018

-- Global constants and sizes
const {
  const.vectorSpaceSize = 300.0
  const.vectorSpaceOffset = 225.0
  const.vectorSpaceGap = 150.0
  const.axisSize = const.vectorSpaceSize * 0.4
  const.distVectorSpaces = 50.0
  const.scaleRatio = 200.0
  const.fontSize = "14pt"
  const.lightenFrac = 0.5
  const.arrowThickness = 2.5
  const.arrowThickness2 = 1.5
  const.arrowheadSize = 0.5
  const.repelWeight = 0.7
  const.textPadding = 15.0
}

-- Global RGB colors in common use
Colors {
    Colors.black = rgba(0.0, 0.0, 0.0, 1.0)
    Colors.lightBlue = rgba(0.1, 0.1, 0.9, 0.1)
    Colors.darkBlue = rgba(0.05, 0.05, 0.6, 0.5)
    Colors.lightGray = rgba(200.0,200.0,200.0,1.0)
    Colors.darkGray = rgba(0.4, 0.4, 0.4, 1.0)

    Colors.gray = rgba(0.8, 0.8, 0.8, 1.0)
    Colors.red = rgba(1.0, 0.0, 0.0, 1.0)
    Colors.pink = rgba(1.0, 0.4, 0.7, 1.0)
    Colors.yellow = rgba(1.0, 1.0, 0.0, 1.0)
    Colors.orange = rgba(1.0, 0.6, 0.0, 1.0)
    Colors.lightorange = rgba(1.0, 0.6, 0.0, 0.25)
    Colors.green = rgba(0.0, 0.8, 0.0, 1.0)
    Colors.blue = rgba(0.0, 0.0, 1.0, 1.0)
    Colors.sky = rgba(0.325, 0.718, 0.769, 1.0)
    Colors.lightsky = rgba(0.325, 0.718, 0.769, 0.25)
    Colors.lightblue = rgba(0.0, 0.0, 1.0, 0.25)
    Colors.cyan = rgba(0.0, 1.0, 1.0, 1.0)
    Colors.purple = rgba(0.5, 0.0, 0.5, 1.0)
    Colors.white = rgba(1.0, 1.0, 1.0, 1.0)
    Colors.none = rgba(0.0, 0.0, 0.0, 0.0)
    Colors.bluegreen = rgba(0.44, 0.68, 0.60, 1.0)
}

VectorSpace U {
    U.thickness = 1.5
    U.axisColor = Colors.darkGray
    U.originX = 0.0

    U.shape = Square {
	x : U.originX
	y : 0.0
        side : const.vectorSpaceSize
        color : Colors.lightBlue
    }

    U.left = U.shape.x + (U.shape.side/2.0)
    U.right = U.shape.x - (U.shape.side/2.0)

    U.xAxis = Arrow {
        startX : U.shape.x
        startY : U.shape.y
        endX : U.shape.x
        endY : U.shape.y + const.axisSize
        thickness : U.thickness
        color : U.axisColor
        arrowheadSize : const.arrowheadSize
    }

    U.yAxis = Arrow {
        startX : U.shape.x
        startY : U.shape.y
        endX : U.shape.x + const.axisSize
        endY : U.shape.y
        thickness : U.thickness
        color : U.axisColor
        arrowheadSize : const.arrowheadSize
    }

    U.zAxis = Arrow {
        startX : U.shape.x
        startY : U.shape.y
        endX : U.shape.x - const.axisSize * 0.5
        endY : U.shape.y - const.axisSize * 0.5
        thickness : U.thickness
        color : U.axisColor
        arrowheadSize : const.arrowheadSize
    }


    U.text = Text {
        string : U.label
        x : U.shape.x - const.axisSize
        y : U.shape.y + const.axisSize
        color : U.axisColor
        fontSize : const.fontSize
    }

    U.textZ = Text {
        string : "z"
        color : Colors.darkGray
        x : U.xAxis.endX
        y : U.xAxis.endY + const.textPadding
    }

    U.textY = Text {
        string : "y"
        color : Colors.darkGray
        x : U.yAxis.endX + const.textPadding
        y : U.yAxis.endY
    }

    U.textX = Text {
        string : "x"
        color : Colors.darkGray
        x : U.zAxis.endX + const.textPadding
        y : U.zAxis.endY
    }

    U.perpMark = Square {
        x : U.shape.x + 5.0
        y : U.shape.y + 5.0
        side : 10.0
        color : Colors.white
	      strokeColor : Colors.black
	      strokeWidth : 1.0
   }


   --U.perpMark2 = Rectangle {
  --      x : 0.0
  --      y : 0.0
  --      color : Colors.white
        --angle = 45.0
        --rotation = 120.0
  -- }

  U.perpLayering1 = U.perpMark below U.xAxis
  U.perpLayering2 = U.perpMark below U.yAxis
  U.perpLayering2 = U.perpMark below U.zAxis
  U.perpLayering3 = U.perpMark above U.shape

}



Vector v
with VectorSpace U
where In(v,U) {
  v.text = Text {
    string : v.label
    color : v.shape.color
    fontSize : const.fontSize
  }

  v.color = sampleColor(1.0, "rgb") -- Full opacity to hide arrowhead layering

  -- v.pad1 = ?
  -- v.pad2 = ?

  v.shape = Arrow {
    startX : U.shape.x
    startY : U.shape.y
    endX : ?
    endY : ?
    -- So the labels can be optimized separately
    -- endX : sampleNum(U.left, U.right) + v.pad1
    -- endY : sampleNum(U.left, U.right) + v.pad2

    thickness : const.arrowThickness
    color : v.color
    arrowheadSize : const.arrowheadSize
  }

   v.vector = (v.shape.endX - v.shape.startX, v.shape.endY - v.shape.startY)
   v.angle = angle(v.vector)

  v.layeringText1 = v.text above U.xAxis
  v.layeringText2 = v.text above U.yAxis
  v.layeringPerp1 = v.shape above U.perpMark

   v.containFn = ensure contains(U.shape, v.shape)

   -- TODO: This constraint is overly harsh -- results in vector addition diagrams that are too similar. Same w/ pairwise repulsion
   v.containLabel = ensure contains(U.shape, v.text)

   -- NOTE: nearHead is actually an objective. Use the other constraint form?
   -- v.labelLocation = ensure nearHead(v.shape, v.text, 0.0, 30.0)
   v.labelLocation = ensure atDist((v.shape.endX, v.shape.endY), v.text, 10.0)

   v.labelAvoidFn = encourage repel(v.shape, v.text, const.repelWeight)

   -- TODO: repel only if it overlaps? (Do a bbox check before doing the expensive sample + sum operation)
   -- v.labelAvoidAxisFn1 = encourage repel(U.xAxis, v.text, const.repelWeight)
   -- v.labelAvoidAxisFn2 = encourage repel(U.yAxis, v.text, const.repelWeight)
   -- v.labelAvoidAxisText = encourage repel(U.text, v.text, const.repelWeight)
}

Vector u
with Vector v; VectorSpace U
where u := neg(v); In(v,U); In(u, U) {
  override u.shape.endX = 2.0 * v.shape.startX - v.shape.endX
  override u.shape.endY = 2.0 * v.shape.startY - v.shape.endY
}

Vector u
with Vector v; Vector w; VectorSpace U
where u := addV(v,w); In(u, U); In(v, U); In(w, U) {
   override u.shape.endX = v.shape.endX + w.shape.endX - U.shape.x
   override u.shape.endY = v.shape.endY + w.shape.endY - U.shape.y
   override u.shape.color = blendColor(v.shape.color, w.shape.color)

    u.slider_v = Arrow {
        endX : u.shape.endX
        endY : u.shape.endY

        startX : w.shape.endX
        startY : w.shape.endY
        thickness : const.arrowThickness2
        color : scaleColor(v.color, const.lightenFrac)
        style : "dashed"
        arrowheadSize : const.arrowheadSize
    }

    u.slider_w = Arrow {
       endX : u.shape.endX
       endY : u.shape.endY

       startX : v.shape.endX
       startY : v.shape.endY
       thickness : const.arrowThickness2
        color : scaleColor(w.color, const.lightenFrac)
       style : "dashed"
       arrowheadSize : const.arrowheadSize
    }

    u.sw_layering = u.slider_w below u.shape
    u.sv_layering = u.slider_v below u.shape
}

LinearMap f
with VectorSpace U; VectorSpace V
where From(f, U, V) {
   override U.shape.x = U.originX - const.vectorSpaceOffset
   override V.shape.x = U.shape.x + const.vectorSpaceSize + const.vectorSpaceGap
   override V.shape.y = U.shape.y

   f.arcRadius = -175.0
   f.left  = [0.65 * U.shape.x + 0.35 * V.shape.x, 0.65 * U.shape.y + 0.35 * V.shape.y]
   -- TODO: the arrowhead doesn't appear at the end of the line, so need to fudge a little.
   f.arrowheadLen = 10.0
   f.right = [0.35 * U.shape.x + 0.65 * V.shape.x - f.arrowheadLen, 0.35 * U.shape.y + 0.65 * V.shape.y]
   f.center = [(get(f.left, 0) + get(f.right, 0)) / 2.0, f.arcRadius]
   f.radius = dist(f.left, f.center)
   f.arcPath = arcPathEuclidean(f.center, f.left, f.right, f.radius, "Open")

   f.shape = Curve {
     pathData : pathFromPoints(f.arcPath)
     fill : Colors.none
     color : Colors.black
     strokeWidth : 2.5
     rightArrowhead : True
     arrowheadSize : const.arrowheadSize
   }

   f.labelOffset = 30.0

   f.text = Text {
     string : f.label
     x : (U.shape.x + V.shape.x) / 2.0
     y : U.shape.y + f.labelOffset
     color : f.shape.color
     fontSize : const.fontSize
   }

   -- define an actual linear map f(x,y) = ax + by
   -- TODO should pick this at random, rather than using a fixed map...
   f.c0 = (-1.0,0.0)
   f.c1 = (0.0,0.5)

   f.orderFn = ensure lessThan(U.shape.x + 20.0, V.shape.x)
}

-- Scalar is not visualized in any concrete way but instead it contains a scalar value
Scalar c {
  c.val = generateRandomReal()
}

Scalar c
with Vector a; Vector b; VectorSpace U
where c := determinant(a,b); In(a,U); In(b,U) {
   c.shape = Curve {
   	   pathData : makeRegionPath(a.shape, b.shape)
	   strokeWidth : 0.0
	   fill : setOpacity(Colors.sky, 0.25)
   }

   c.text = Text {
     string : c.label
     -- Normalize for origin
     x : (a.shape.endX + b.shape.endX - a.shape.startX) / 2.0
     y : (a.shape.endY + b.shape.endY - a.shape.startY) / 2.0
     color : Colors.sky
     fontSize : const.fontSize
   }

   c.layering = c.text above c.shape
}

Scalar c
with Vector a; Vector b; VectorSpace U
where c := innerProduct(a,b); In(a,U); In(b,U) {
    override c.val = len(a.shape) * calcVectorsAngleCos(a.shape.startX,a.shape.startY,a.shape.endX,a.shape.endY, b.shape.startX,b.shape.startY,b.shape.endX,b.shape.endY) / 200.0
    c.line1 = Line {
        startX : b.shape.startX
        startY : b.shape.startY
        endX :  b.shape.endX * c.val - b.shape.startX * (c.val - 1.0)
        endY : b.shape.endY * c.val - b.shape.startY * (c.val - 1.0)
        thickness : 2.5
        style : "dashed"
    }

    c.line2 = Line {
        startX : a.shape.endX
        startY : a.shape.endY
        endX : b.shape.endX * c.val - b.shape.startX * (c.val - 1.0)
        endY : b.shape.endY * c.val - b.shape.startY * (c.val - 1.0)
        thickness : 2.5
        style : "dashed"
    }
}

Scalar c
with Vector a;  VectorSpace U
where c := norm(a); In(a,U) {
   c.norm = len(a.shape)
   override c.val = c.norm / const.scaleRatio
   c.padding = 20.0

   -- c.shape = Brace {
   c.shape = Line {
     startX : a.shape.startX
     startY : a.shape.startY + c.padding
     endX : a.shape.endX
     endY : a.shape.endY + c.padding
     thickness : 1.5
     color : setOpacity(a.shape.color, 0.75)
   }

   c.text = Text {
        string : c.label
        x : c.padding + midpointX(c.shape)
        y : c.padding + midpointY(c.shape)
        color : c.shape.color
        fontSize : const.fontSize
   }
}

Vector u
with Scalar c; VectorSpace U; Vector v
where u := scale(c, v); In(u, U); In(v, U) {
    override u.shape.endX = v.shape.endX * c.val - v.shape.startX * (c.val - 1.0)
    override u.shape.endY = v.shape.endY * c.val - v.shape.startY * (c.val - 1.0)

    c.layeringScale = u.shape below v.shape
}

Vector u; Vector v
with VectorSpace U
where Orthogonal(u, v); In(u, U); In(v, U) {
      -- TODO: fix this angle calculation so it works when both vectors are in quadrants 2 or 3
      LOCAL.angle = min(u.angle, v.angle)
      LOCAL.len = 20.0

      -- Scale by side len. Then translate so default square's corner is at (0,0).
      -- Then rotate by the vector angle. Then translate to the local origin of the vector space.
      LOCAL.shape = SquareTransform {
	  -- TODO: unintuitive behavior; need to set these constants otherwise shape is offscreen

	  -- TODO: this currently isn't working because we commented out transforms in computedProperties (they were slowing down the system)

	  -- x : 0.0
	  -- y : 0.0
	  -- side : 100.0
	  -- rotation : 0.0

	  x : 0.0
	  y : 0.0
	  side : 1.0
	  rotation : 0.0
	  transform : andThen(andThen(andThen(scale(LOCAL.len, LOCAL.len), translate(LOCAL.len / 2.0, LOCAL.len / 2.0)), rotate(LOCAL.angle)), translate(U.shape.x, U.shape.y))
	  color : Colors.none
	  strokeColor : Colors.black
	  strokeWidth : 0.1
      }

      -- LOCAL.perpFn = ensure equals(dot(u.vector, v.vector), 0.0)
      LOCAL.perpFn = encourage equal(dot(u.vector, v.vector), 0.0)

      LOCAL.layering1 = v.shape above LOCAL.shape
      LOCAL.layering2 = u.shape above LOCAL.shape
}

-- No label overlaps with any other label, vector, slider vector, or axis line. (This doesn't match the same vector twice, right?)
-- Only if in the same vectorspace!
Vector u1; Vector u2
with VectorSpace U
where In(u1, U); In(u2, U) {
       -- NOTE: This matches (u,v) and (v,u), generating double objectives
       -- TODO: this doesn't quite work b/c it doesn't account for the thickness of the label
       -- TODO: this currently makes diagrams too symmetrical. Maybe do a second labeling pass?

      LOCAL.labelAvoidSegFn = encourage repel(u1.shape, u2.text, const.repelWeight)
      LOCAL.labelAvoidTextFn = encourage repel(u1.text, u2.text, 1000.0 * const.repelWeight)

      -- LOCAL.labelAvoidTextFn = encourage disjointPoly(u1.text, u2.text)
}

-- For vectors that are mapped from one vector space to another,
-- actually define the target geometry by applying a linear
-- map to the input geometry.  This map is given by the two (column)
-- vectors L.c0, L.c1, which together make a matrix.

Vector y
with Vector x; VectorSpace X; VectorSpace Y; LinearMap f
where In(x, X); In(y, Y); From(f, X, Y); y := apply(f, x) {

   -- apply the linear map
   override y.shape.endX = dot( f.c0, x.vector ) + Y.shape.x
   override y.shape.endY = dot( f.c1, x.vector ) + Y.shape.y

   -- update the underlying vector (TODO check this)
   override y.vector = (y.shape.endX - y.shape.startX, y.shape.end - y.shape.startY)

   -- also inherit the color
   override y.shape.color = x.shape.color
}

-- v4 = v3, so use its sliders for v3, and hide its label
-- NOTE: this matching scales very badly...
Vector v4
with Vector u1; Vector u2; Vector u3; Vector v1; Vector v2; Vector v3; LinearMap f; VectorSpace U; VectorSpace V
where From(f, U, V); u3 := addV(u1, u2); v1 := apply(f, u1); v2 := apply(f, u2); v3 := apply(f, u3); v4 := addV(v1, v2) {
      override v4.slider_v.color = u3.slider_v.color
      override v4.slider_w.color = u3.slider_w.color
      override v4.text.string = "\\text{ }" -- TODO: fails on empty label
      delete v4.containFn
      delete v4.containLabel
      delete v4.labelLocation
      delete v4.labelAvoidFn
}

-- v2 = v3, so have its scalar be colored the same, and hide its label
Vector v3
with Vector u1; Vector u2; Vector v1; Vector v2; Scalar c; LinearMap f; VectorSpace U; VectorSpace V
where From(f, U, V); u2 := scale(c, u1); v1 := apply(f, u1); v2 := apply(f, u2); v3 := scale(c, v1) {
      v3.layeringFn1 = v3.shape below v2.shape
      override v3.shape.color = v2.shape.color
      override v3.text.string = "\\text{ }" -- TODO: fails on empty label
      delete v3.containFn
      delete v3.containLabel
      delete v3.labelLocation
      delete v3.labelAvoidFn
}
